#include <sys/system.h>
#include <sys/filerw.h>

#ifdef PTGUI_USING_DFS_FILERW
#include <dfs_posix.h>

/* standard file read/write */
struct pt_filerw_stdio
{
	/* inherit from pt_filerw */
	struct pt_filerw	parent;

	int					fd;
	pt_bool_t			eof;
};

static int stdio_seek(struct pt_filerw *context, pt_off_t offset, int whence)
{
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;
	int stdio_whence[3] =
	{
		SEEK_SET,
		SEEK_CUR,
		SEEK_END
	};

	if (whence < PTGUI_FILE_SEEK_SET || whence > PTGUI_FILE_SEEK_END) {
		return -1;
	}

	return lseek(stdio_filerw->fd, offset, stdio_whence[whence]);
}

static int stdio_read(struct pt_filerw *context, void *ptr, pt_size_t size, pt_size_t maxnum)
{
	int result;

	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;

	/* end of file */
	if (stdio_filerw->eof == PT_TRUE) {
		return -1;
	}

	result = read(stdio_filerw->fd, ptr, size * maxnum);
	if (result == 0) {
		stdio_filerw->eof = PT_TRUE;
	}

	return result;
}

static int stdio_write(struct pt_filerw *context, const void *ptr, pt_size_t size, pt_size_t num)
{
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;

	return write(stdio_filerw->fd, (pt_char_t *)ptr, size * num);
}

static int stdio_tell(struct pt_filerw *context)
{
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;

	return lseek(stdio_filerw->fd, 0, SEEK_CUR);
}

static int stdio_eof(struct pt_filerw *context)
{
	int result;
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;

	if (stdio_filerw->eof == PT_TRUE) {
		result = 1;
	}
	else {
		result = -1;
	}

	return result;
}

static int stdio_close(struct pt_filerw *context)
{
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;

	if (stdio_filerw) {
		close(stdio_filerw->fd);
		pt_free(stdio_filerw);

		return 0;
	}

	return -1;
}
#elif defined(PTGUI_FEATHER_USING_STDIO_FILERW)
#include <stdio.h>

/* standard file read/write */
struct pt_filerw_stdio
{
	/* inherit from pt_filerw */
	struct pt_filerw	parent;

	FILE				*fp;
};

static int stdio_seek(struct pt_filerw *context, pt_off_t offset, int whence)
{
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;
	int stdio_whence[3] =
	{
		SEEK_SET,
		SEEK_CUR,
		SEEK_END
	};

	if (whence < PTGUI_FILE_SEEK_SET || whence > PTGUI_FILE_SEEK_END) {
		return -1;
	}

	if (fseek(stdio_filerw->fp, offset, stdio_whence[whence]) == 0) {
		return ftell(stdio_filerw->fp);
	}

	return -1;
}

static int stdio_read(struct pt_filerw *context, void *ptr, pt_size_t size, pt_size_t maxnum)
{
	size_t nread;
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;

	nread = fread(ptr, size, maxnum, stdio_filerw->fp);
	if (nread == 0 && ferror(stdio_filerw->fp)) {
		return -1;
	}

	return nread;
}

static int stdio_write(struct pt_filerw *context, const void *ptr, pt_size_t size, pt_size_t num)
{
	size_t nwrote;
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;

	nwrote = fwrite(ptr, size, num, stdio_filerw->fp);

	if (nwrote == 0 && ferror(stdio_filerw->fp)) {
		return -1;
	}

	return nwrote;
}

static int stdio_tell(struct pt_filerw *context)
{
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;

	return ftell(stdio_filerw->fp);
}

static int stdio_eof(struct pt_filerw *context)
{
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;

	return feof(stdio_filerw->fp);
}

static int stdio_close(struct pt_filerw *context)
{
	struct pt_filerw_stdio *stdio_filerw = (struct pt_filerw_stdio *)context;

	if (stdio_filerw) {
		fclose(stdio_filerw->fp);
		pt_free(stdio_filerw);

		return 0;
	}

	return -1;
}
#endif

/* memory file read/write */
struct pt_filerw_mem
{
	/* inherit from pt_filerw */
	struct pt_filerw	parent;

	const pt_uint8_t	*mem_base,
						 *mem_position,
						 *mem_end;
};

static int mem_seek(struct pt_filerw *context, pt_off_t offset, int whence)
{
	const pt_uint8_t *newpos;
	struct pt_filerw_mem *mem = (struct pt_filerw_mem *)context;

	PT_ASSERT(mem != PT_NULL);

	switch (whence) {
	case PTGUI_FILE_SEEK_SET:
		newpos = mem->mem_base + offset;
		break;

	case PTGUI_FILE_SEEK_CUR:
		newpos = mem->mem_position + offset;
		break;

	case PTGUI_FILE_SEEK_END:
		newpos = mem->mem_end + offset;
		break;

	default:
		return -1;
	}

	if (newpos < mem->mem_base) {
		newpos = mem->mem_base;
	}

	if (newpos > mem->mem_end) {
		newpos = mem->mem_end;
	}

	mem->mem_position = newpos;
	return mem->mem_position - mem->mem_base;
}

static int mem_read(struct pt_filerw *context, void *ptr, pt_size_t size, pt_size_t maxnum)
{
	int total_bytes;
	int mem_available;
	struct pt_filerw_mem *mem = (struct pt_filerw_mem *)context;

	total_bytes = (maxnum * size);
	if ((maxnum <= 0) || (size <= 0) || ((total_bytes / maxnum) != size)) {
		return -1;
	}

	mem_available = mem->mem_end - mem->mem_position;
	if (total_bytes > mem_available) {
		total_bytes = mem_available;
	}

	pt_memcpy(ptr, mem->mem_position, total_bytes);
	mem->mem_position += total_bytes;

	return (total_bytes / size);
}

static int mem_write(struct pt_filerw *context, const void *ptr, pt_size_t size, pt_size_t num)
{
#if 0
	struct pt_filerw_mem* mem = (struct pt_filerw_mem*)context;

	if ((mem->mem_position + (num * size)) > mem->mem_end)
	{
		num = (mem->mem_end - mem->mem_position)/size;
	}

	rt_memcpy(mem->mem_position, ptr, num*size);
	mem->mem_position += num*size;

	return num;
#else
	return 0; /* not support memory write */
#endif
}

static int mem_tell(struct pt_filerw *context)
{
	struct pt_filerw_mem *mem = (struct pt_filerw_mem *)context;

	return mem->mem_position - mem->mem_base;
}

static int mem_eof(struct pt_filerw *context)
{
	struct pt_filerw_mem *mem = (struct pt_filerw_mem *)context;

	return mem->mem_position >= mem->mem_end;
}

static int mem_close(struct pt_filerw *context)
{
	struct pt_filerw_mem *mem = (struct pt_filerw_mem *)context;

	if (mem != PT_NULL) {
		pt_free(mem);
		return 0;
	}

	return -1;
}

const pt_uint8_t * pt_filerw_mem_getdata(struct pt_filerw *context)
{
	struct pt_filerw_mem *mem = (struct pt_filerw_mem *)context;

	/* check whether it's a memory filerw */
	if (mem->parent.read != mem_read) {
		return PT_NULL;
	}

	return mem->mem_base;
}

/* file read/write public interface */
#ifdef PTGUI_USING_DFS_FILERW
static int parse_mode(const pt_char_t *mode)
{
	int f = 0;

	for (; ;) {
		switch (*mode) {
		case 0:
			return f;
		case 'b':
			break;
		case 'r':
			f = O_RDONLY; break;
		case 'w':
			f = O_WRONLY | O_CREAT | O_TRUNC; break;
		case 'a':
			f = O_WRONLY | O_CREAT | O_APPEND; break;
		case '+':
			f = (f & (~O_WRONLY)) | O_RDWR; break;
		}

		++mode;
	}
}

struct pt_filerw * pt_filerw_create_file(const pt_char_t *filename, const pt_char_t *mode)
{
	int fd;
	struct pt_filerw_stdio *rw;

	PT_ASSERT(filename != PT_NULL);

	rw = PT_NULL;
	fd = open(filename, parse_mode(mode), 0);

	if (fd >= 0) {
		rw = (struct pt_filerw_stdio *)pt_malloc(sizeof(struct pt_filerw_stdio));
		if (rw != PT_NULL) {
			rw->parent.seek = stdio_seek;
			rw->parent.read = stdio_read;
			rw->parent.write = stdio_write;
			rw->parent.tell = stdio_tell;
			rw->parent.close = stdio_close;
			rw->parent.eof = stdio_eof;

			rw->fd = fd;
			rw->eof = PT_FALSE;
		}
	}

	return &(rw->parent);
}
#elif defined(PTGUI_FEATHER_USING_STDIO_FILERW)
struct pt_filerw * pt_filerw_create_file(const pt_char_t *filename, const pt_char_t *mode)
{
	FILE *fp;
	struct pt_filerw_stdio *rw;

	PT_ASSERT(filename != PT_NULL);

	rw = PT_NULL;
	fp = fopen(filename, mode);

	if (fp != NULL) {
		rw = (struct pt_filerw_stdio *)pt_malloc(sizeof(struct pt_filerw_stdio));
		if (rw != PT_NULL) {
			rw->parent.seek = stdio_seek;
			rw->parent.read = stdio_read;
			rw->parent.write = stdio_write;
			rw->parent.tell = stdio_tell;
			rw->parent.close = stdio_close;
			rw->parent.eof = stdio_eof;

			rw->fp = fp;
		}
	}

	return &(rw->parent);
}
#endif

struct pt_filerw * pt_filerw_create_mem(const pt_uint8_t *mem, pt_size_t size)
{
	struct pt_filerw_mem *rw;
	PT_ASSERT(mem != PT_NULL);

	rw = (struct pt_filerw_mem *)pt_malloc(sizeof(struct pt_filerw_mem));
	if (rw != PT_NULL) {
		rw->parent.seek = mem_seek;
		rw->parent.read = mem_read;
		rw->parent.write = mem_write;
		rw->parent.tell = mem_tell;
		rw->parent.eof = mem_eof;
		rw->parent.close = mem_close;

		rw->mem_base = mem;
		rw->mem_position = mem;
		rw->mem_end = mem + size;
	}

	return &(rw->parent);
}

int pt_filerw_seek(struct pt_filerw *context, pt_off_t offset, int whence)
{
	PT_ASSERT(context != PT_NULL);

	return context->seek(context, offset, whence);
}

int pt_filerw_read(struct pt_filerw *context, void *buffer, pt_size_t size, pt_size_t count)
{
	PT_ASSERT(context != PT_NULL);

	return context->read(context, buffer, size, count);
}

int pt_filerw_write(struct pt_filerw *context, const void *buffer, pt_size_t size, pt_size_t count)
{
	PT_ASSERT(context != PT_NULL);

	return context->write(context, buffer, size, count);
}

int pt_filerw_eof(struct pt_filerw *context)
{
	PT_ASSERT(context != PT_NULL);

	return context->eof(context);
}

int pt_filerw_tell(struct pt_filerw *context)
{
	PT_ASSERT(context != PT_NULL);

	return context->tell(context);
}

int pt_filerw_close(struct pt_filerw *context)
{
	int result;

	PT_ASSERT(context != PT_NULL);

	/* close context */
	result = context->close(context);
	if (result != 0) {
		/* close file failed */
		return -1;
	}

	return 0;
}
